using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Multiplicity.Runtime.Serialization.Binary;

namespace Multiplicity.Reflection {
    public class PortableExecutable {

        readonly byte[] image;

        readonly DosHeader dosHeader;
        readonly CoffHeader coffHeader;
        readonly PEHeader peHeader;
        readonly List<PESection> sections;

        public PortableExecutable(byte[] image) {

            Throw.IfIsNullOrEmpty(image);

            this.image = image;

            using (BinaryParser br = new BinaryParser(new BufferSegment(image))) {

                dosHeader = DosHeader.Parse(br);

                br.SkipTo(dosHeader.CoffHeaderOffset);

                coffHeader = br.ReadRecord<CoffHeader>();
                peHeader = PEHeader.Parse(br);

                sections = new List<PESection>();
                for (int i = 0; i < coffHeader.NumberOfSections; i++) {
                    sections.Add(br.ReadRecord<PESection>());
                }
            }
        }

        public DosHeader DosHeader {
            get { return dosHeader; }
        }

        public CoffHeader CoffHeader {
            get { return coffHeader; }
        }

        public PEHeader PEHeader {
            get { return peHeader; }
        }

        public IList<PESection> Sections {
            get { return sections; }
        }

        protected BinaryParser OpenDirectory(PEDataDirectory dir) {
            PESection section = FindSectionByRva(dir.VirtualAddress);   
            if (section == null) {
                InvalidPEFileException.ThrowInvalidRva();
            }

            uint index = (section.PointerToRawData + (dir.VirtualAddress - section.VirtualAdress));
            uint count = (dir.Size);
            
            return new BinaryParser(new BufferSegment(image, index, count), index);
        }

        protected BinaryParser OpenSection(PESection section) {
            Throw.IfIsNull(section);

            uint index = (section.PointerToRawData);
            uint count = (section.SizeOfRawData);

            return new BinaryParser(new BufferSegment(image, index, count));
        }

        protected internal byte[] Image {
            get { return image; }
        }

        protected PESection FindSectionByRva(uint rva) {
            for (int i = 0; i < sections.Count; i++) {
                uint sectionStart = sections[i].VirtualAdress;
                uint sectionEnd = sectionStart + sections[i].VirtualSize;

                if (rva >= sectionStart && rva < sectionEnd) {
                    return sections[i];
                }
            }

            return null;
        }
    }
}